home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2018 July / PCgo 07-2018 CD-ROM Germany.iso / nw.pak / Unnamed File 000155.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  28.8 KB  |  882 lines

  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. /**
  6.  * @fileoverview Touch Handler. Class that handles all touch events and
  7.  * uses them to interpret higher level gestures and behaviors. TouchEvent is a
  8.  * built in mobile safari type:
  9.  * http://developer.apple.com/safari/library/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html.
  10.  * This class is intended to work with all webkit browsers, tested on Chrome and
  11.  * iOS.
  12.  *
  13.  * The following types of gestures are currently supported.  See the definition
  14.  * of TouchHandler.EventType for details.
  15.  *
  16.  * Single Touch:
  17.  *      This provides simple single-touch events.  Any secondary touch is
  18.  *      ignored.
  19.  *
  20.  * Drag:
  21.  *      A single touch followed by some movement. This behavior will handle all
  22.  *      of the required events and report the properties of the drag to you
  23.  *      while the touch is happening and at the end of the drag sequence. This
  24.  *      behavior will NOT perform the actual dragging (redrawing the element)
  25.  *      for you, this responsibility is left to the client code.
  26.  *
  27.  * Long press:
  28.  *     When your element is touched and held without any drag occuring, the
  29.  *     LONG_PRESS event will fire.
  30.  */
  31.  
  32. // Use an anonymous function to enable strict mode just for this file (which
  33. // will be concatenated with other files when embedded in Chrome)
  34. cr.define('cr.ui', function() {
  35.   'use strict';
  36.  
  37.   /**
  38.    * A TouchHandler attaches to an Element, listents for low-level touch (or
  39.    * mouse) events and dispatching higher-level events on the element.
  40.    * @param {!Element} element The element to listen on and fire events
  41.    * for.
  42.    * @constructor
  43.    */
  44.   function TouchHandler(element) {
  45.     /**
  46.      * @type {!Element}
  47.      * @private
  48.      */
  49.     this.element_ = element;
  50.  
  51.     /**
  52.      * The absolute sum of all touch y deltas.
  53.      * @type {number}
  54.      * @private
  55.      */
  56.     this.totalMoveY_ = 0;
  57.  
  58.     /**
  59.      * The absolute sum of all touch x deltas.
  60.      * @type {number}
  61.      * @private
  62.      */
  63.     this.totalMoveX_ = 0;
  64.  
  65.     /**
  66.      * An array of tuples where the first item is the horizontal component of a
  67.      * recent relevant touch and the second item is the touch's time stamp. Old
  68.      * touches are removed based on the max tracking time and when direction
  69.      * changes.
  70.       * @type {!Array.<number>}
  71.       * @private
  72.       */
  73.     this.recentTouchesX_ = [];
  74.  
  75.     /**
  76.      * An array of tuples where the first item is the vertical component of a
  77.      * recent relevant touch and the second item is the touch's time stamp. Old
  78.      * touches are removed based on the max tracking time and when direction
  79.      * changes.
  80.      * @type {!Array.<number>}
  81.      * @private
  82.      */
  83.     this.recentTouchesY_ = [];
  84.  
  85.     /**
  86.      * Used to keep track of all events we subscribe to so we can easily clean
  87.      * up
  88.      * @type {EventTracker}
  89.      * @private
  90.      */
  91.     this.events_ = new EventTracker();
  92.   }
  93.  
  94.  
  95.   /**
  96.    * DOM Events that may be fired by the TouchHandler at the element
  97.    */
  98.   TouchHandler.EventType = {
  99.     // Fired whenever the element is touched as the only touch to the device.
  100.     // enableDrag defaults to false, set to true to permit dragging.
  101.     TOUCH_START: 'touchHandler:touch_start',
  102.  
  103.     // Fired when an element is held for a period of time.  Prevents dragging
  104.     // from occuring (even if enableDrag was set to true).
  105.     LONG_PRESS: 'touchHandler:long_press',
  106.  
  107.     // If enableDrag was set to true at TOUCH_START, DRAG_START will fire when
  108.     // the touch first moves sufficient distance.  enableDrag is set to true but
  109.     // can be reset to false to cancel the drag.
  110.     DRAG_START: 'touchHandler:drag_start',
  111.  
  112.     // If enableDrag was true after DRAG_START, DRAG_MOVE will fire whenever the
  113.     // touch is moved.
  114.     DRAG_MOVE: 'touchHandler:drag_move',
  115.  
  116.     // Fired just before TOUCH_END when a drag is released.  Correlates 1:1 with
  117.     // a DRAG_START.
  118.     DRAG_END: 'touchHandler:drag_end',
  119.  
  120.     // Fired whenever a touch that is being tracked has been released.
  121.     // Correlates 1:1 with a TOUCH_START.
  122.     TOUCH_END: 'touchHandler:touch_end',
  123.  
  124.     // Fired whenever the element is tapped in a short time and no dragging is
  125.     // detected.
  126.     TAP: 'touchHandler:tap'
  127.   };
  128.  
  129.  
  130.   /**
  131.    * The type of event sent by TouchHandler
  132.    * @constructor
  133.    * @extends {Event}
  134.    * @param {string} type The type of event (one of cr.ui.Grabber.EventType).
  135.    * @param {boolean} bubbles Whether or not the event should bubble.
  136.    * @param {number} clientX The X location of the touch.
  137.    * @param {number} clientY The Y location of the touch.
  138.    * @param {!Element} touchedElement The element at the current location of the
  139.    *        touch.
  140.    */
  141.   TouchHandler.Event = function(type, bubbles, clientX, clientY,
  142.       touchedElement) {
  143.     var event = document.createEvent('Event');
  144.     event.initEvent(type, bubbles, true);
  145.     event.__proto__ = TouchHandler.Event.prototype;
  146.  
  147.     /**
  148.      * The X location of the touch affected
  149.      * @type {number}
  150.      */
  151.     event.clientX = clientX;
  152.  
  153.     /**
  154.      * The Y location of the touch affected
  155.      * @type {number}
  156.      */
  157.     event.clientY = clientY;
  158.  
  159.     /**
  160.      * The element at the current location of the touch.
  161.      * @type {!Element}
  162.      */
  163.     event.touchedElement = touchedElement;
  164.  
  165.     return event;
  166.   };
  167.  
  168.   TouchHandler.Event.prototype = {
  169.     __proto__: Event.prototype,
  170.  
  171.     /**
  172.      * For TOUCH_START and DRAG START events, set to true to enable dragging or
  173.      * false to disable dragging.
  174.      * @type {boolean|undefined}
  175.      */
  176.     enableDrag: undefined,
  177.  
  178.     /**
  179.      * For DRAG events, provides the horizontal component of the
  180.      * drag delta. Drag delta is defined as the delta of the start touch
  181.      * position and the current drag position.
  182.      * @type {number|undefined}
  183.      */
  184.     dragDeltaX: undefined,
  185.  
  186.     /**
  187.      * For DRAG events, provides the vertical component of the
  188.      * drag delta.
  189.      * @type {number|undefined}
  190.      */
  191.     dragDeltaY: undefined
  192.   };
  193.  
  194.   /**
  195.    * Maximum movement of touch required to be considered a tap.
  196.    * @type {number}
  197.    * @private
  198.    */
  199.   TouchHandler.MAX_TRACKING_FOR_TAP_ = 8;
  200.  
  201.  
  202.   /**
  203.    * The maximum number of ms to track a touch event. After an event is older
  204.    * than this value, it will be ignored in velocity calculations.
  205.    * @type {number}
  206.    * @private
  207.    */
  208.   TouchHandler.MAX_TRACKING_TIME_ = 250;
  209.  
  210.  
  211.   /**
  212.    * The maximum number of touches to track.
  213.    * @type {number}
  214.    * @private
  215.    */
  216.   TouchHandler.MAX_TRACKING_TOUCHES_ = 5;
  217.  
  218.  
  219.   /**
  220.    * The maximum velocity to return, in pixels per millisecond, that is used
  221.    * to guard against errors in calculating end velocity of a drag. This is a
  222.    * very fast drag velocity.
  223.    * @type {number}
  224.    * @private
  225.    */
  226.   TouchHandler.MAXIMUM_VELOCITY_ = 5;
  227.  
  228.  
  229.   /**
  230.    * The velocity to return, in pixel per millisecond, when the time stamps on
  231.    * the events are erroneous. The browser can return bad time stamps if the
  232.    * thread is blocked for the duration of the drag. This is a low velocity to
  233.    * prevent the content from moving quickly after a slow drag. It is less
  234.    * jarring if the content moves slowly after a fast drag.
  235.    * @type {number}
  236.    * @private
  237.    */
  238.   TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ = 1;
  239.  
  240.   /**
  241.    * The time, in milliseconds, that a touch must be held to be considered
  242.    * 'long'.
  243.    * @type {number}
  244.    * @private
  245.    */
  246.   TouchHandler.TIME_FOR_LONG_PRESS_ = 500;
  247.  
  248.   TouchHandler.prototype = {
  249.     /**
  250.      * If defined, the identifer of the single touch that is active.  Note that
  251.      * 0 is a valid touch identifier - it should not be treated equivalently to
  252.      * undefined.
  253.      * @type {number|undefined}
  254.      * @private
  255.      */
  256.     activeTouch_: undefined,
  257.  
  258.     /**
  259.      * @type {boolean|undefined}
  260.      * @private
  261.      */
  262.     tracking_: undefined,
  263.  
  264.     /**
  265.      * @type {number|undefined}
  266.      * @private
  267.      */
  268.     startTouchX_: undefined,
  269.  
  270.     /**
  271.      * @type {number|undefined}
  272.      * @private
  273.      */
  274.     startTouchY_: undefined,
  275.  
  276.     /**
  277.      * @type {number|undefined}
  278.      * @private
  279.      */
  280.     endTouchX_: undefined,
  281.  
  282.     /**
  283.      * @type {number|undefined}
  284.      * @private
  285.      */
  286.     endTouchY_: undefined,
  287.  
  288.     /**
  289.      * Time of the touchstart event.
  290.      * @type {number|undefined}
  291.      * @private
  292.      */
  293.     startTime_: undefined,
  294.  
  295.     /**
  296.      * The time of the touchend event.
  297.      * @type {number|undefined}
  298.      * @private
  299.      */
  300.     endTime_: undefined,
  301.  
  302.     /**
  303.      * @type {number|undefined}
  304.      * @private
  305.      */
  306.     lastTouchX_: undefined,
  307.  
  308.     /**
  309.      * @type {number|undefined}
  310.      * @private
  311.      */
  312.     lastTouchY_: undefined,
  313.  
  314.     /**
  315.      * @type {number|undefined}
  316.      * @private
  317.      */
  318.     lastMoveX_: undefined,
  319.  
  320.     /**
  321.      * @type {number|undefined}
  322.      * @private
  323.      */
  324.     lastMoveY_: undefined,
  325.  
  326.     /**
  327.      * @type {number|undefined}
  328.      * @private
  329.      */
  330.     longPressTimeout_: undefined,
  331.  
  332.     /**
  333.      * If defined and true, the next click event should be swallowed
  334.      * @type {boolean|undefined}
  335.      * @private
  336.      */
  337.     swallowNextClick_: undefined,
  338.  
  339.     /**
  340.      * @type {boolean}
  341.      * @private
  342.      */
  343.     draggingEnabled_: false,
  344.  
  345.     /**
  346.      * Start listenting for events.
  347.      * @param {boolean=} opt_capture True if the TouchHandler should listen to
  348.      *      during the capture phase.
  349.      * @param {boolean=} opt_mouse True if the TouchHandler should generate
  350.      *      events for mouse input (in addition to touch input).
  351.      */
  352.     enable: function(opt_capture, opt_mouse) {
  353.       var capture = !!opt_capture;
  354.  
  355.       // Just listen to start events for now. When a touch is occuring we'll
  356.       // want to be subscribed to move and end events on the document, but we
  357.       // don't want to incur the cost of lots of no-op handlers on the document.
  358.       this.events_.add(this.element_, 'touchstart', this.onStart_.bind(this),
  359.                        capture);
  360.       if (opt_mouse) {
  361.         this.events_.add(this.element_, 'mousedown',
  362.                          this.mouseToTouchCallback_(this.onStart_.bind(this)),
  363.                          capture);
  364.       }
  365.  
  366.       // If the element is long-pressed, we may need to swallow a click
  367.       this.events_.add(this.element_, 'click', this.onClick_.bind(this), true);
  368.     },
  369.  
  370.     /**
  371.      * Stop listening to all events.
  372.      */
  373.     disable: function() {
  374.       this.stopTouching_();
  375.       this.events_.removeAll();
  376.     },
  377.  
  378.     /**
  379.      * Wraps a callback with translations of mouse events to touch events.
  380.      * NOTE: These types really should be function(Event) but then we couldn't
  381.      * use this with bind (which operates on any type of function).  Doesn't
  382.      * JSDoc support some sort of polymorphic types?
  383.      * @param {Function} callback The event callback.
  384.      * @return {Function} The wrapping callback.
  385.      * @private
  386.      */
  387.     mouseToTouchCallback_: function(callback) {
  388.       return function(e) {
  389.         // Note that there may be synthesizes mouse events caused by touch
  390.         // events (a mouseDown after a touch-click).  We leave it up to the
  391.         // client to worry about this if it matters to them (typically a short
  392.         // mouseDown/mouseUp without a click is no big problem and it's not
  393.         // obvious how we identify such synthesized events in a general way).
  394.         var touch = {
  395.           // any fixed value will do for the identifier - there will only
  396.           // ever be a single active 'touch' when using the mouse.
  397.           identifier: 0,
  398.           clientX: e.clientX,
  399.           clientY: e.clientY,
  400.           target: e.target
  401.         };
  402.         e.touches = [];
  403.         e.targetTouches = [];
  404.         e.changedTouches = [touch];
  405.         if (e.type != 'mouseup') {
  406.           e.touches[0] = touch;
  407.           e.targetTouches[0] = touch;
  408.         }
  409.         callback(e);
  410.       };
  411.     },
  412.  
  413.     /**
  414.      * Begin tracking the touchable element, it is eligible for dragging.
  415.      * @private
  416.      */
  417.     beginTracking_: function() {
  418.       this.tracking_ = true;
  419.     },
  420.  
  421.     /**
  422.      * Stop tracking the touchable element, it is no longer dragging.
  423.      * @private
  424.      */
  425.     endTracking_: function() {
  426.       this.tracking_ = false;
  427.       this.dragging_ = false;
  428.       this.totalMoveY_ = 0;
  429.       this.totalMoveX_ = 0;
  430.     },
  431.  
  432.     /**
  433.      * Reset the touchable element as if we never saw the touchStart
  434.      * Doesn't dispatch any end events - be careful of existing listeners.
  435.      */
  436.     cancelTouch: function() {
  437.       this.stopTouching_();
  438.       this.endTracking_();
  439.       // If clients needed to be aware of this, we could fire a cancel event
  440.       // here.
  441.     },
  442.  
  443.     /**
  444.      * Record that touching has stopped
  445.      * @private
  446.      */
  447.     stopTouching_: function() {
  448.       // Mark as no longer being touched
  449.       this.activeTouch_ = undefined;
  450.  
  451.       // If we're waiting for a long press, stop
  452.       window.clearTimeout(this.longPressTimeout_);
  453.  
  454.       // Stop listening for move/end events until there's another touch.
  455.       // We don't want to leave handlers piled up on the document.
  456.       // Note that there's no harm in removing handlers that weren't added, so
  457.       // rather than track whether we're using mouse or touch we do both.
  458.       this.events_.remove(document, 'touchmove');
  459.       this.events_.remove(document, 'touchend');
  460.       this.events_.remove(document, 'touchcancel');
  461.       this.events_.remove(document, 'mousemove');
  462.       this.events_.remove(document, 'mouseup');
  463.     },
  464.  
  465.     /**
  466.      * Touch start handler.
  467.      * @param {!TouchEvent} e The touchstart event.
  468.      * @private
  469.      */
  470.     onStart_: function(e) {
  471.       // Only process single touches.  If there is already a touch happening, or
  472.       // two simultaneous touches then just ignore them.
  473.       if (e.touches.length > 1)
  474.         // Note that we could cancel an active touch here.  That would make
  475.         // simultaneous touch behave similar to near-simultaneous. However, if
  476.         // the user is dragging something, an accidental second touch could be
  477.         // quite disruptive if it cancelled their drag.  Better to just ignore
  478.         // it.
  479.         return;
  480.  
  481.       // It's still possible there could be an active "touch" if the user is
  482.       // simultaneously using a mouse and a touch input.
  483.       if (this.activeTouch_ !== undefined)
  484.         return;
  485.  
  486.       var touch = e.targetTouches[0];
  487.       this.activeTouch_ = touch.identifier;
  488.  
  489.       // We've just started touching so shouldn't swallow any upcoming click
  490.       if (this.swallowNextClick_)
  491.         this.swallowNextClick_ = false;
  492.  
  493.       this.disableTap_ = false;
  494.  
  495.       // Sign up for end/cancel notifications for this touch.
  496.       // Note that we do this on the document so that even if the user drags
  497.       // their finger off the element, we'll still know what they're doing.
  498.       if (e.type == 'mousedown') {
  499.         this.events_.add(document, 'mouseup',
  500.             this.mouseToTouchCallback_(this.onEnd_.bind(this)), false);
  501.       } else {
  502.         this.events_.add(document, 'touchend', this.onEnd_.bind(this), false);
  503.         this.events_.add(document, 'touchcancel', this.onEnd_.bind(this),
  504.             false);
  505.       }
  506.  
  507.       // This timeout is cleared on touchEnd and onDrag
  508.       // If we invoke the function then we have a real long press
  509.       window.clearTimeout(this.longPressTimeout_);
  510.       this.longPressTimeout_ = window.setTimeout(
  511.           this.onLongPress_.bind(this),
  512.           TouchHandler.TIME_FOR_LONG_PRESS_);
  513.  
  514.       // Dispatch the TOUCH_START event
  515.       this.draggingEnabled_ =
  516.           !!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch);
  517.  
  518.       // We want dragging notifications
  519.       if (e.type == 'mousedown') {
  520.         this.events_.add(document, 'mousemove',
  521.             this.mouseToTouchCallback_(this.onMove_.bind(this)), false);
  522.       } else {
  523.         this.events_.add(document, 'touchmove', this.onMove_.bind(this), false);
  524.       }
  525.  
  526.       this.startTouchX_ = this.lastTouchX_ = touch.clientX;
  527.       this.startTouchY_ = this.lastTouchY_ = touch.clientY;
  528.       this.startTime_ = e.timeStamp;
  529.  
  530.       this.recentTouchesX_ = [];
  531.       this.recentTouchesY_ = [];
  532.       this.recentTouchesX_.push(touch.clientX, e.timeStamp);
  533.       this.recentTouchesY_.push(touch.clientY, e.timeStamp);
  534.  
  535.       this.beginTracking_();
  536.     },
  537.  
  538.     /**
  539.      * Given a list of Touches, find the one matching our activeTouch
  540.      * identifier. Note that Chrome currently always uses 0 as the identifier.
  541.      * In that case we'll end up always choosing the first element in the list.
  542.      * @param {TouchList} touches The list of Touch objects to search.
  543.      * @return {!Touch|undefined} The touch matching our active ID if any.
  544.      * @private
  545.      */
  546.     findActiveTouch_: function(touches) {
  547.       assert(this.activeTouch_ !== undefined, 'Expecting an active touch');
  548.       // A TouchList isn't actually an array, so we shouldn't use
  549.       // Array.prototype.filter/some, etc.
  550.       for (var i = 0; i < touches.length; i++) {
  551.         if (touches[i].identifier == this.activeTouch_)
  552.           return touches[i];
  553.       }
  554.       return undefined;
  555.     },
  556.  
  557.     /**
  558.      * Touch move handler.
  559.      * @param {!TouchEvent} e The touchmove event.
  560.      * @private
  561.      */
  562.     onMove_: function(e) {
  563.       if (!this.tracking_)
  564.         return;
  565.  
  566.       // Our active touch should always be in the list of touches still active
  567.       assert(this.findActiveTouch_(e.touches), 'Missing touchEnd');
  568.  
  569.       var that = this;
  570.       var touch = this.findActiveTouch_(e.changedTouches);
  571.       if (!touch)
  572.         return;
  573.  
  574.       var clientX = touch.clientX;
  575.       var clientY = touch.clientY;
  576.  
  577.       var moveX = this.lastTouchX_ - clientX;
  578.       var moveY = this.lastTouchY_ - clientY;
  579.       this.totalMoveX_ += Math.abs(moveX);
  580.       this.totalMoveY_ += Math.abs(moveY);
  581.       this.lastTouchX_ = clientX;
  582.       this.lastTouchY_ = clientY;
  583.  
  584.       var couldBeTap =
  585.           this.totalMoveY_ <= TouchHandler.MAX_TRACKING_FOR_TAP_ ||
  586.           this.totalMoveX_ <= TouchHandler.MAX_TRACKING_FOR_TAP_;
  587.  
  588.       if (!couldBeTap)
  589.         this.disableTap_ = true;
  590.  
  591.       if (this.draggingEnabled_ && !this.dragging_ && !couldBeTap) {
  592.         // If we're waiting for a long press, stop
  593.         window.clearTimeout(this.longPressTimeout_);
  594.  
  595.         // Dispatch the DRAG_START event and record whether dragging should be
  596.         // allowed or not.  Note that this relies on the current value of
  597.         // startTouchX/Y - handlers may use the initial drag delta to determine
  598.         // if dragging should be permitted.
  599.         this.dragging_ = this.dispatchEvent_(
  600.             TouchHandler.EventType.DRAG_START, touch);
  601.  
  602.         if (this.dragging_) {
  603.           // Update the start position here so that drag deltas have better
  604.           // values but don't touch the recent positions so that velocity
  605.           // calculations can still use touchstart position in the time and
  606.           // distance delta.
  607.           this.startTouchX_ = clientX;
  608.           this.startTouchY_ = clientY;
  609.           this.startTime_ = e.timeStamp;
  610.         } else {
  611.           this.endTracking_();
  612.         }
  613.       }
  614.  
  615.       if (this.dragging_) {
  616.         this.dispatchEvent_(TouchHandler.EventType.DRAG_MOVE, touch);
  617.  
  618.         this.removeTouchesInWrongDirection_(this.recentTouchesX_,
  619.             this.lastMoveX_, moveX);
  620.         this.removeTouchesInWrongDirection_(this.recentTouchesY_,
  621.             this.lastMoveY_, moveY);
  622.         this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  623.         this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  624.         this.recentTouchesX_.push(clientX, e.timeStamp);
  625.         this.recentTouchesY_.push(clientY, e.timeStamp);
  626.       }
  627.  
  628.       this.lastMoveX_ = moveX;
  629.       this.lastMoveY_ = moveY;
  630.     },
  631.  
  632.     /**
  633.      * Filters the provided recent touches array to remove all touches except
  634.      * the last if the move direction has changed.
  635.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  636.      *     item is the x or y component of the recent touch and the second item
  637.      *     is the touch time stamp.
  638.      * @param {number|undefined} lastMove The x or y component of the previous
  639.      *     move.
  640.      * @param {number} recentMove The x or y component of the most recent move.
  641.      * @private
  642.      */
  643.     removeTouchesInWrongDirection_: function(recentTouches, lastMove,
  644.         recentMove) {
  645.       if (lastMove && recentMove && recentTouches.length > 2 &&
  646.           (lastMove > 0 ^ recentMove > 0)) {
  647.         recentTouches.splice(0, recentTouches.length - 2);
  648.       }
  649.     },
  650.  
  651.     /**
  652.      * Filters the provided recent touches array to remove all touches older
  653.      * than the max tracking time or the 5th most recent touch.
  654.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  655.      *     item is the x or y component of the recent touch and the second item
  656.      *     is the touch time stamp.
  657.      * @param {number} recentTime The time of the most recent event.
  658.      * @private
  659.      */
  660.     removeOldTouches_: function(recentTouches, recentTime) {
  661.       while (recentTouches.length && recentTime - recentTouches[1] >
  662.           TouchHandler.MAX_TRACKING_TIME_ ||
  663.           recentTouches.length >
  664.               TouchHandler.MAX_TRACKING_TOUCHES_ * 2) {
  665.         recentTouches.splice(0, 2);
  666.       }
  667.     },
  668.  
  669.     /**
  670.      * Touch end handler.
  671.      * @param {!TouchEvent} e The touchend event.
  672.      * @private
  673.      */
  674.     onEnd_: function(e) {
  675.       var that = this;
  676.       assert(this.activeTouch_ !== undefined, 'Expect to already be touching');
  677.  
  678.       // If the touch we're tracking isn't changing here, ignore this touch end.
  679.       var touch = this.findActiveTouch_(e.changedTouches);
  680.       if (!touch) {
  681.         // In most cases, our active touch will be in the 'touches' collection,
  682.         // but we can't assert that because occasionally two touchend events can
  683.         // occur at almost the same time with both having empty 'touches' lists.
  684.         // I.e., 'touches' seems like it can be a bit more up-to-date than the
  685.         // current event.
  686.         return;
  687.       }
  688.  
  689.       // This is touchEnd for the touch we're monitoring
  690.       assert(!this.findActiveTouch_(e.touches),
  691.              'Touch ended also still active');
  692.  
  693.       // Indicate that touching has finished
  694.       this.stopTouching_();
  695.  
  696.       if (this.tracking_) {
  697.         var clientX = touch.clientX;
  698.         var clientY = touch.clientY;
  699.  
  700.         if (this.dragging_) {
  701.           this.endTime_ = e.timeStamp;
  702.           this.endTouchX_ = clientX;
  703.           this.endTouchY_ = clientY;
  704.  
  705.           this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  706.           this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  707.  
  708.           this.dispatchEvent_(TouchHandler.EventType.DRAG_END, touch);
  709.  
  710.           // Note that in some situations we can get a click event here as well.
  711.           // For now this isn't a problem, but we may want to consider having
  712.           // some logic that hides clicks that appear to be caused by a touchEnd
  713.           // used for dragging.
  714.         }
  715.  
  716.         this.endTracking_();
  717.       }
  718.       this.draggingEnabled_ = false;
  719.  
  720.       // Note that we dispatch the touchEnd event last so that events at
  721.       // different levels of semantics nest nicely (similar to how DOM
  722.       // drag-and-drop events are nested inside of the mouse events that trigger
  723.       // them).
  724.       this.dispatchEvent_(TouchHandler.EventType.TOUCH_END, touch);
  725.       if (!this.disableTap_)
  726.         this.dispatchEvent_(TouchHandler.EventType.TAP, touch);
  727.     },
  728.  
  729.     /**
  730.      * Get end velocity of the drag. This method is specific to drag behavior,
  731.      * so if touch behavior and drag behavior is split then this should go with
  732.      * drag behavior. End velocity is defined as deltaXY / deltaTime where
  733.      * deltaXY is the difference between endPosition and the oldest recent
  734.      * position, and deltaTime is the difference between endTime and the oldest
  735.      * recent time stamp.
  736.      * @return {Object} The x and y velocity.
  737.      */
  738.     getEndVelocity: function() {
  739.       // Note that we could move velocity to just be an end-event parameter.
  740.       var velocityX = this.recentTouchesX_.length ?
  741.           (this.endTouchX_ - this.recentTouchesX_[0]) /
  742.           (this.endTime_ - this.recentTouchesX_[1]) : 0;
  743.       var velocityY = this.recentTouchesY_.length ?
  744.           (this.endTouchY_ - this.recentTouchesY_[0]) /
  745.           (this.endTime_ - this.recentTouchesY_[1]) : 0;
  746.  
  747.       velocityX = this.correctVelocity_(velocityX);
  748.       velocityY = this.correctVelocity_(velocityY);
  749.  
  750.       return {
  751.         x: velocityX,
  752.         y: velocityY
  753.       };
  754.     },
  755.  
  756.     /**
  757.      * Correct erroneous velocities by capping the velocity if we think it's too
  758.      * high, or setting it to a default velocity if know that the event data is
  759.      * bad.
  760.      * @param {number} velocity The x or y velocity component.
  761.      * @return {number} The corrected velocity.
  762.      * @private
  763.      */
  764.     correctVelocity_: function(velocity) {
  765.       var absVelocity = Math.abs(velocity);
  766.  
  767.       // We add to recent touches for each touchstart and touchmove. If we have
  768.       // fewer than 3 touches (6 entries), we assume that the thread was blocked
  769.       // for the duration of the drag and we received events in quick succession
  770.       // with the wrong time stamps.
  771.       if (absVelocity > TouchHandler.MAXIMUM_VELOCITY_) {
  772.         absVelocity = this.recentTouchesY_.length < 3 ?
  773.             TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ :
  774.                 TouchHandler.MAXIMUM_VELOCITY_;
  775.       }
  776.       return absVelocity * (velocity < 0 ? -1 : 1);
  777.     },
  778.  
  779.     /**
  780.      * Handler when an element has been pressed for a long time
  781.      * @private
  782.      */
  783.     onLongPress_: function() {
  784.       // Swallow any click that occurs on this element without an intervening
  785.       // touch start event.  This simple click-busting technique should be
  786.       // sufficient here since a real click should have a touchstart first.
  787.       this.swallowNextClick_ = true;
  788.       this.disableTap_ = true;
  789.  
  790.       // Dispatch to the LONG_PRESS
  791.       assert(typeof this.startTouchX_ == 'number');
  792.       assert(typeof this.startTouchY_ == 'number');
  793.       this.dispatchEventXY_(TouchHandler.EventType.LONG_PRESS, this.element_,
  794.           /** @type {number} */(this.startTouchX_),
  795.           /** @type {number} */(this.startTouchY_));
  796.     },
  797.  
  798.     /**
  799.      * Click handler - used to swallow clicks after a long-press
  800.      * @param {!Event} e The click event.
  801.      * @private
  802.      */
  803.     onClick_: function(e) {
  804.       if (this.swallowNextClick_) {
  805.         e.preventDefault();
  806.         e.stopPropagation();
  807.         this.swallowNextClick_ = false;
  808.       }
  809.     },
  810.  
  811.     /**
  812.      * Dispatch a TouchHandler event to the element
  813.      * @param {string} eventType The event to dispatch.
  814.      * @param {Touch} touch The touch triggering this event.
  815.      * @return {boolean|undefined} The value of enableDrag after dispatching
  816.      *         the event.
  817.      * @private
  818.      */
  819.     dispatchEvent_: function(eventType, touch) {
  820.  
  821.       // Determine which element was touched.  For mouse events, this is always
  822.       // the event/touch target.  But for touch events, the target is always the
  823.       // target of the touchstart (and it's unlikely we can change this
  824.       // since the common implementation of touch dragging relies on it). Since
  825.       // touch is our primary scenario (which we want to emulate with mouse),
  826.       // we'll treat both cases the same and not depend on the target.
  827.       /** @type {Element} */
  828.       var touchedElement;
  829.       if (eventType == TouchHandler.EventType.TOUCH_START) {
  830.         touchedElement = assertInstanceof(touch.target, Element);
  831.       } else {
  832.         touchedElement = assert(this.element_.ownerDocument.
  833.             elementFromPoint(touch.clientX, touch.clientY));
  834.       }
  835.  
  836.       return this.dispatchEventXY_(eventType, touchedElement, touch.clientX,
  837.           touch.clientY);
  838.     },
  839.  
  840.     /**
  841.      * Dispatch a TouchHandler event to the element
  842.      * @param {string} eventType The event to dispatch.
  843.      * @param {!Element} touchedElement
  844.      * @param {number} clientX The X location for the event.
  845.      * @param {number} clientY The Y location for the event.
  846.      * @return {boolean|undefined} The value of enableDrag after dispatching
  847.      *         the event.
  848.      * @private
  849.      */
  850.     dispatchEventXY_: function(eventType, touchedElement, clientX, clientY) {
  851.       var isDrag = (eventType == TouchHandler.EventType.DRAG_START ||
  852.           eventType == TouchHandler.EventType.DRAG_MOVE ||
  853.           eventType == TouchHandler.EventType.DRAG_END);
  854.  
  855.       // Drag events don't bubble - we're really just dragging the element,
  856.       // not affecting its parent at all.
  857.       var bubbles = !isDrag;
  858.  
  859.       var event = new TouchHandler.Event(eventType, bubbles, clientX, clientY,
  860.           touchedElement);
  861.  
  862.       // Set enableDrag when it can be overridden
  863.       if (eventType == TouchHandler.EventType.TOUCH_START)
  864.         event.enableDrag = false;
  865.       else if (eventType == TouchHandler.EventType.DRAG_START)
  866.         event.enableDrag = true;
  867.  
  868.       if (isDrag) {
  869.         event.dragDeltaX = clientX - this.startTouchX_;
  870.         event.dragDeltaY = clientY - this.startTouchY_;
  871.       }
  872.  
  873.       this.element_.dispatchEvent(event);
  874.       return event.enableDrag;
  875.     }
  876.   };
  877.  
  878.   return {
  879.     TouchHandler: TouchHandler
  880.   };
  881. });
  882.